package org.windowkit.toolkit.event;

import java.awt.Component;
import java.awt.ItemSelectable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;

import org.ddevil.data.Filter;


/**
 * Re-dispatches Swing events to the listeners.
 * @author rich
 * @author eric
 */
public class EventDispatcher implements ActionListener, FocusListener,
		ItemListener, KeyListener, ListSelectionListener, TreeSelectionListener,
        MouseListener{

	/** We will use this as the source of our events */
	private final Component source;

	/** Hold the EventListeners for this class. */
	private final EventListenerList listenerList;

	/** Filters */
	private Filter<ActionEvent> actionEventFilter;
	private Filter<KeyEvent> keyEventFilter;
	private Filter<FocusEvent> focusEventFilter;
	private Filter<ItemEvent> itemEventFilter;
	private Filter<ListSelectionEvent> listEventFilter;
	private Filter<TreeSelectionEvent> treeSelectionEventFilter;
    private Filter<MouseEvent> mouseEventFilter;

	/**
	 * Create an EventDispatcher that will re-dispatch events with out resourcing them.
	 */
	public EventDispatcher() {
		this(null);
	}

	/**
	 * Creates an ActionEventDispatcher where the given object will be used as
	 * the source for dispatched events.
	 *
	 * @param source
	 *            The object we are dispatching events for. This will allow us
	 *            to set the source of the event as a different source than the
	 *            "true" event source. If source is <code>null</code> events
	 *            will not be re-sourced.
	 */
	public EventDispatcher(final Component source) {
		this.source = source;
		listenerList = new EventListenerList();
	}

	/**
	 * Add an ActionListener.
	 *
	 * @param al
	 *            The ActionListener to add.
	 */
	public void addActionListener(final ActionListener al) {
		if (al != null) {
			listenerList.add(ActionListener.class, al);
		}
	}

	/**
	 * Add a FocusListener.
	 *
	 * @param fl
	 *            The FocusListener to add.
	 */
	public void addFocusListener(final FocusListener fl) {
		if (fl != null) {
			listenerList.add(FocusListener.class, fl);
		}
	}

	/**
	 * Add an ItemListener.
	 *
	 * @param il
	 *            The ItemListener to add.
	 */
	public void addItemListener(final ItemListener il) {
		if (source != null && !(source instanceof ItemSelectable)) {
			throw new IllegalArgumentException(
					"In order to use EventDispatcher for ItemListener's, "
							+ "the source must implement ItemSelectable.");
		}
		if (il != null) {
			listenerList.add(ItemListener.class, il);
		}
	}

	/**
	 * Add a KeyListener.
	 *
	 * @param kl
	 *            The KeyListener to add.
	 */
	public void addKeyListener(final KeyListener kl) {
		if (kl != null) {
			listenerList.add(KeyListener.class, kl);
		}
	}

	/**
	 * Add a ListSelectionListener.
	 *
	 * @param l
	 *            The ListSelectionListener to add.
	 */
	public void addListSelectionListener(final ListSelectionListener l) {
		if (l != null) {
			listenerList.add(ListSelectionListener.class, l);
		}
	}

	/**
	 * Add a TreeSelectionListener.
	 *
	 * @param l
	 *            The TreeSelectionListener to remove.
	 */
	public void addTreeSelectionListener(final TreeSelectionListener l) {
		if (l != null) {
			listenerList.add(TreeSelectionListener.class, l);
		}
	}

    /**
     * Add a MouseListener.
     *
     * @param l
     */
    public void addMouseListener(final MouseListener l){
        if(l != null) {
			listenerList.add(MouseListener.class, l);
		}
    }

	/**
	 * Remove an ActionListener
	 *
	 * @param al
	 *            The ActionListener to remove.
	 */
	public void removeActionListener(final ActionListener al) {
		if (al != null) {
			listenerList.remove(ActionListener.class, al);
		}
	}

	/**
	 * Remove a FocusListener.
	 *
	 * @param fl
	 *            The FocusListener to remove.
	 */
	public void removeFocusListener(final FocusListener fl) {
		if (fl != null) {
			listenerList.remove(FocusListener.class, fl);
		}
	}

	/**
	 * Remove an ItemListener.
	 *
	 * @param il
	 *            The ItemListener to remove.
	 */
	public void removeItemListener(final ItemListener il) {
		if (il != null) {
			listenerList.remove(ItemListener.class, il);
		}
	}

	/**
	 * Remove a KeyListener.
	 *
	 * @param kl
	 *            The KeyListener to remove.
	 */
	public void removeKeyListener(final KeyListener kl) {
		if (kl != null) {
			listenerList.remove(KeyListener.class, kl);
		}
	}

	/**
	 * Remove a ListSelectionListener.
	 *
	 * @param l
	 *            The ListSelectionListener to remove.
	 */
	public void removeListSelectionListener(final ListSelectionListener l) {
		if (l != null) {
			listenerList.remove(ListSelectionListener.class, l);
		}
	}

	/**
	 * Remove a TreeSelectionListener.
	 *
	 * @param l
	 *            The TreeSelectionListener to remove.
	 */
	public void removeTreeSelectionListener(final TreeSelectionListener l) {
		if (l != null) {
			listenerList.remove(TreeSelectionListener.class, l);
		}
	}

    /**
     * Remove a MouseListener.
     *
     * @param l
     */
    public void removeMouseListener(final MouseListener l){
        if( l != null) {
			listenerList.remove(MouseListener.class, l);
		}
    }

	/**
	 * Set the {@link Filter} used to filter {@link ActionEvent}'s before they
	 * are re-dispatched.
	 *
	 * @param filter
	 *            Only events that are accepted by this Filter will be
	 *            re-dispatched.
	 */
	public void setActionEventFilter(final Filter<ActionEvent> filter) {
		this.actionEventFilter = filter;
	}

	/**
	 * Set the {@link Filter} used to filter {@link FocusEvent}'s before they
	 * are re-dispatched.
	 *
	 * @param filter
	 *            Only events that are accepted by this Filter will be
	 *            re-dispatched.
	 */
	public void setFocusEventFilter(final Filter<FocusEvent> filter) {
		this.focusEventFilter = filter;
	}

	/**
	 * Set the {@link Filter} used to filter {@link ItemEvent}'s before they
	 * are re-dispatched.
	 *
	 * @param filter
	 *            Only events that are accepted by this Filter will be
	 *            re-dispatched.
	 */
	public void setItemEventFilter(final Filter<ItemEvent> filter) {
		this.itemEventFilter = filter;
	}

	/**
	 * Set the {@link Filter} used to filter {@link KeyEvent}'s before they are
	 * re-dispatched.
	 *
	 * @param filter
	 *            Only events that are accepted by this Filter will be
	 *            re-dispatched.
	 */
	public void setKeyEventFilter(final Filter<KeyEvent> filter) {
		this.keyEventFilter = filter;
	}

	/**
	 * Set the {@link Filter} used to filter {@link ListSelectionEvent}'s
	 * before they are re-dispatched.
	 *
	 * @param filter
	 *            Only events that are accepted by this Filter will be
	 *            re-dispatched.
	 */
	public void setListSelectionListenerFilter(final Filter<ListSelectionEvent> filter) {
		listEventFilter = filter;
	}

    /**
     * Set the {@link Filter} used to filter {@link MouseEvent}'s
     * before they are re-dispatched.
     *
     * @param filter
     */
    public void setMouseEventFilter(final Filter<MouseEvent> filter){
        mouseEventFilter = filter;
    }

	/**
	 * {@inheritDoc}
	 */
	public void actionPerformed(ActionEvent evt) {
		if (actionEventFilter == null || actionEventFilter.accept(evt)) {
			if (source != null){
				evt = new ActionEvent(source, evt.getID(), evt.getActionCommand(),
                        evt.getWhen(), evt.getModifiers());
            }
			// Dispatch event to all registered listeners.
			for (ActionListener al : listenerList
					.getListeners(ActionListener.class)) {
				al.actionPerformed(evt);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void focusGained(FocusEvent e) {
		if (focusEventFilter == null || focusEventFilter.accept(e)) {
			if (source != null) {
				e = resourceFocusEvent(e);
			}
			for (FocusListener l : listenerList
					.getListeners(FocusListener.class)) {
				l.focusGained(e);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void focusLost(FocusEvent e) {
		if (focusEventFilter == null || focusEventFilter.accept(e)) {
			if (source != null) {
				e = resourceFocusEvent(e);
			}
			for (FocusListener l : listenerList
					.getListeners(FocusListener.class)) {
				l.focusLost(e);
			}
		}
	}

    private FocusEvent resourceFocusEvent(final FocusEvent e){
        return new FocusEvent(source, e.getID(), e.isTemporary(),
                e.getOppositeComponent());
    }

	/**
	 * {@inheritDoc}
	 */
	public void itemStateChanged(ItemEvent e) {
		if (itemEventFilter == null || itemEventFilter.accept(e)) {
			if (source != null) {
				e = resourceItemEvent(e);
			}
			for (ItemListener l : listenerList.getListeners(ItemListener.class)) {
				l.itemStateChanged(e);
			}
		}
	}


	private ItemEvent resourceItemEvent(final ItemEvent e) {
		return new ItemEvent((ItemSelectable) source, e.getID(), e.getItem(), e
				.getStateChange());
	}

	/**
	 * {@inheritDoc}
	 */
	public void keyTyped(KeyEvent e) {
		if (keyEventFilter == null || keyEventFilter.accept(e)) {
			if (source != null) {
				e = resourceKeyEvent(e);
			}
			for (KeyListener l : listenerList.getListeners(KeyListener.class)) {
				l.keyTyped(e);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void keyPressed(KeyEvent e) {
		if (keyEventFilter == null || keyEventFilter.accept(e)) {
			if (source != null) {
				e = resourceKeyEvent(e);
			}
			for (KeyListener l : listenerList.getListeners(KeyListener.class)) {
				l.keyPressed(e);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void keyReleased(KeyEvent e) {
		if (keyEventFilter == null || keyEventFilter.accept(e)) {
			if (source != null) {
				e = resourceKeyEvent(e);
			}
			for (KeyListener l : listenerList.getListeners(KeyListener.class)) {
				l.keyReleased(e);
			}
		}
	}

    private KeyEvent resourceKeyEvent(final KeyEvent e){
        return new KeyEvent( source, e.getID(), e.getWhen(), e
                .getModifiers(), e.getKeyCode(), e.getKeyChar(), e
                .getKeyLocation());
    }

	/**
	 * {@inheritDoc}
	 */
	public void valueChanged(final ListSelectionEvent e) {
		ListSelectionEvent lse;
		if (listEventFilter == null || listEventFilter.accept(e)) {
			if (source != null) {
				lse = new ListSelectionEvent(source, e.getFirstIndex(), e
						.getLastIndex(), e.getValueIsAdjusting());
			} else {
				lse = e;
			}
			for (ListSelectionListener lsl : listenerList
					.getListeners(ListSelectionListener.class)) {
				lsl.valueChanged(lse);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void valueChanged(final TreeSelectionEvent e) {
		TreeSelectionEvent tse;
		if (treeSelectionEventFilter == null
				|| treeSelectionEventFilter.accept(e)) {
			if (source != null) {
				tse = (TreeSelectionEvent) e.cloneWithSource(source);
			} else {
				tse = e;
			}
			for (TreeSelectionListener tsl : listenerList
					.getListeners(TreeSelectionListener.class)) {
				tsl.valueChanged(tse);
			}
		}
	}

    /**
     * {@inheritDoc}
     */
    public void mouseClicked(MouseEvent e) {
        if(mouseEventFilter == null || mouseEventFilter.accept(e)){
            if(source != null){
                e = resourceMouseEvent(e);
            }
            for (MouseListener l :
                    listenerList.getListeners(MouseListener.class)) {
                l.mouseClicked(e);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void mousePressed(MouseEvent e) {
        if(mouseEventFilter == null || mouseEventFilter.accept(e)){
            if(source != null){
                e = resourceMouseEvent(e);
            }
            for (MouseListener l :
                    listenerList.getListeners(MouseListener.class)) {
                l.mousePressed(e);
            }
        }

    }

    /**
     * {@inheritDoc}
     */
    public void mouseReleased(MouseEvent e) {
        if(mouseEventFilter == null || mouseEventFilter.accept(e)){
            if(source != null){
                e = resourceMouseEvent(e);
            }
            for (MouseListener l :
                    listenerList.getListeners(MouseListener.class)) {
                l.mouseReleased(e);
            }
        }

    }

    /**
     * {@inheritDoc}
     */
    public void mouseEntered(MouseEvent e) {
        if(mouseEventFilter == null || mouseEventFilter.accept(e)){
            if(source != null){
                e = resourceMouseEvent(e);
            }
            for (MouseListener l :
                    listenerList.getListeners(MouseListener.class)) {
                l.mouseEntered(e);
            }
        }

    }

    /**
     * {@inheritDoc}
     */
    public void mouseExited(MouseEvent e) {
        if(mouseEventFilter == null || mouseEventFilter.accept(e)){
            if(source != null){
                e = resourceMouseEvent(e);
            }
            for (MouseListener l :
                    listenerList.getListeners(MouseListener.class)) {
                l.mouseExited(e);
            }
        }

    }

    /**
     * Create a copy of the given mouse event with the source changed
     * to our Component source.
     *
     * @param e
     * @return
     */
    private MouseEvent resourceMouseEvent(final MouseEvent e){
        return new MouseEvent(source, e.getID(), e.getWhen(), e.getModifiers(),
                e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(),
                e.getButton());
    }

}